# -*- coding: utf-8 -*-
"""
功能要点：
- 顺序提取剧集播放页面中的 iframe -> video src（多次重试与多种选择器）
- 使用 Selenium 模拟按键触发下载（Tab / Enter）
- 随机等待（0.5 ~ 3s）以模拟真实用户行为
- 下载监测（检测新文件与 .crdownload），等待下载完成（可自定义超时）
- 所有视频下载任务完成后再等待 300 秒以确保文件写入完成
- 下载完成后按顺序重命名为 "第1集.mp4", "第2集.mp4", ...
- 将提取到的 url / info 保存为文本和 json
- 详细的打印输出 / 异常跟踪，保留原有逻辑（重试、回退等）
- **修改重点：下载触发后不再等待单个文件完成，仅在所有下载启动后等待一次**
"""

import os
import time
import json
import random
import traceback
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


class AutoVideoDownloader:
    def __init__(self, download_dir="downloads"):
        """
        初始化：设置下载目录并启动 Selenium 浏览器。
        请确保 chromedriver.exe 放在脚本相同目录下，或修改 chromedriver_path。
        """
        self.download_dir = download_dir
        if not os.path.exists(self.download_dir):
            os.makedirs(self.download_dir, exist_ok=True)

        self.driver = None
        self.setup_selenium()

    def setup_selenium(self):
        """配置 Chrome（Selenium）并启动浏览器实例"""
        chrome_options = Options()
        chrome_options.add_argument(
            '--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
            '(KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36'
        )
        chrome_options.add_argument('--disable-blink-features=AutomationControlled')
        chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
        chrome_options.add_experimental_option('useAutomationExtension', False)

        prefs = {
            "download.default_directory": os.path.abspath(self.download_dir),
            "download.prompt_for_download": False,
            "download.directory_upgrade": True,
            "safebrowsing.enabled": False,
            "profile.default_content_settings.popups": 0,
            "profile.content_settings.exceptions.automatic_downloads.*.setting": 1,
            "download_restrictions": 0
        }
        chrome_options.add_experimental_option("prefs", prefs)

        try:
            # 默认 chromedriver 与脚本同目录
            current_dir = os.path.dirname(os.path.abspath(__file__))
            chromedriver_path = os.path.join(current_dir, 'chromedriver.exe')

            if not os.path.exists(chromedriver_path):
                raise FileNotFoundError(f"找不到 chromedriver: {chromedriver_path}，请将 chromedriver.exe 放在脚本目录或修改路径。")

            service = Service(executable_path=chromedriver_path)
            self.driver = webdriver.Chrome(service=service, options=chrome_options)

            # 隐藏 webdriver 标识
            try:
                self.driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
            except Exception:
                pass

            print("✅ 浏览器初始化成功")
            print(f"📁 下载目录: {os.path.abspath(self.download_dir)}")

        except Exception as e:
            print(f"❌ 浏览器初始化失败: {e}")
            raise

    def random_sleep(self, min_s=1.0, max_s=3.0):
        """随机睡眠，参数为最小和最大值（秒）"""
        delay = random.uniform(min_s, max_s)
        time.sleep(delay)
        return delay

    def get_episode_list(self, detail_url):
        """
        获取剧集列表（返回字典列表，每项包含 title, url, href）。
        使用 BS4 解析 detail_url 页面内 class 为 stui-content__playlist 的 ul 节点。
        """
        try:
            self.driver.get(detail_url)
            WebDriverWait(self.driver, 10).until(
                EC.presence_of_element_located((By.CLASS_NAME, "stui-content__playlist"))
            )

            soup = BeautifulSoup(self.driver.page_source, 'lxml')
            # 有些站点类名可能有变化，尝试常见组合
            episode_container = soup.find('ul', class_='stui-content__playlist clearfix') \
                                or soup.find('ul', class_='stui-content__playlist') \
                                or soup.find('ul', attrs={'class': lambda v: v and 'playlist' in v})

            if not episode_container:
                print("⚠️ 未找到剧集列表 (未检测到 stui-content__playlist)")
                return None

            episodes = []
            for li in episode_container.find_all('li'):
                link = li.find('a')
                if link and link.get('href'):
                    href = link['href']
                    # 合成完整URL（若以 / 开头）
                    url = 'https://www.libvio.cc' + href if href.startswith('/') else href
                    episodes.append({
                        'title': link.get_text().strip(),
                        'url': url,
                        'href': href
                    })

            return episodes

        except Exception as e:
            print(f"❌ 获取剧集列表失败: {e}")
            print(traceback.format_exc())
            return None

    def extract_video_url(self, play_url, title, retry_count=3):
        """
        提取单个视频真实地址（video.src）：
        - 支持多种 iframe 选择器与重试逻辑
        - 切换 iframe 并查找 <video> 标签的 src 属性
        - 返回 dict: {title, video_url, cookies, referer, iframe_src} 或 None
        """
        for attempt in range(retry_count):
            try:
                print(f"🔎 正在提取: {title} (尝试 {attempt + 1}/{retry_count})")
                self.driver.get(play_url)

                # 为避免页面未完全加载，给个短等待（但不是固定长等待）
                self.random_sleep(0.8, 2.0)

                wait = WebDriverWait(self.driver, 15)

                # 尝试多种 iframe 选择器（越精确的放前面）
                iframe_selectors = [
                    "iframe[src*='/vid/plyr/']",
                    "iframe[src*='vid/plyr']",
                    "iframe[src*='vid']",
                    "iframe"
                ]

                iframe_elem = None
                iframe_src = None
                for selector in iframe_selectors:
                    try:
                        iframe_candidate = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, selector)))
                        src = iframe_candidate.get_attribute("src")
                        # 若找到了 src，使用它
                        if iframe_candidate and src:
                            iframe_elem = iframe_candidate
                            iframe_src = src
                            print(f"  - 使用选择器找到 iframe: {selector} (src: {src[:80] + '...' if src and len(src) > 80 else src})")
                            break
                    except Exception:
                        # 若当前选择器未找到，继续下一个
                        continue

                if not iframe_elem:
                    print(f"  ⚠️ 未找到 iframe（第 {attempt+1} 次尝试）")
                    if attempt < retry_count - 1:
                        print("  - 等待并重试...")
                        self.random_sleep(1.5, 3.0)
                        continue
                    return None

                # 切换到 iframe
                try:
                    self.driver.switch_to.frame(iframe_elem)
                except Exception:
                    # 当我们通过 WebElement 切换失败时，尝试通过 index 或 src
                    try:
                        self.driver.switch_to.default_content()
                        # 通过 src 找到 iframe 再切换
                        iframe_elements = self.driver.find_elements(By.TAG_NAME, 'iframe')
                        switched = False
                        for ife in iframe_elements:
                            try:
                                if ife.get_attribute('src') == iframe_src:
                                    self.driver.switch_to.frame(ife)
                                    switched = True
                                    break
                            except Exception:
                                continue
                        if not switched:
                            # 尝试切换到第一个 iframe
                            self.driver.switch_to.frame(0)
                    except Exception:
                        pass

                # 等待 iframe 内的 video 标签
                try:
                    video_element = WebDriverWait(self.driver, 10).until(
                        EC.presence_of_element_located((By.TAG_NAME, "video"))
                    )
                    video_url = video_element.get_attribute("src")
                    # 一些站点 video.src 为空，需要查找 <source> 子节点
                    if not video_url:
                        try:
                            source_elem = video_element.find_element(By.TAG_NAME, "source")
                            video_url = source_elem.get_attribute("src")
                        except Exception:
                            pass

                    if video_url:
                        print(f"✅ 成功提取: {title}")
                        # 收集 cookies
                        cookies = self.driver.get_cookies()
                        # 回到主文档
                        self.driver.switch_to.default_content()
                        return {
                            'title': title,
                            'video_url': video_url,
                            'cookies': cookies,
                            'referer': play_url,
                            'iframe_src': iframe_src
                        }
                    else:
                        print(f"  ⚠️ 找到 video 元素但未取得 src（第 {attempt+1} 次尝试）")
                except Exception as e:
                    print(f"  ⚠️ iframe 内查找 video 失败: {e}")

                # 回到默认上下文，准备重试或结束
                try:
                    self.driver.switch_to.default_content()
                except Exception:
                    pass

                if attempt < retry_count - 1:
                    print("  - 等待并重试...")
                    self.random_sleep(2.0, 4.0)

            except Exception as e:
                print(f"❌ 提取失败 {title} (尝试 {attempt + 1}): {e}")
                print(traceback.format_exc())
                try:
                    self.driver.switch_to.default_content()
                except Exception:
                    pass
                if attempt < retry_count - 1:
                    print("  - 准备重试...")
                    self.random_sleep(2.0, 4.0)

        # 所有尝试失败
        return None

    def extract_all_video_urls_sequential(self, episodes):
        """
        顺序提取所有视频地址（避免并发导致的页面干扰）；
        返回 (all_video_info, failed_episodes)
        """
        print(f"\n📥 开始顺序提取 {len(episodes)} 个视频地址...")
        all_video_info = []
        failed_episodes = []

        for i, episode in enumerate(episodes, 1):
            print(f"\n[{i}/{len(episodes)}] 处理: {episode['title']}")
            # 若不是第一集，随机短等待，避免请求过快
            if i > 1:
                d = self.random_sleep(0.5, 1.8)
                print(f"  - 随机等待 {d:.2f}s 后继续")

            # 尝试提取（保留默认 retry）
            video_info = self.extract_video_url(episode['url'], episode['title'])

            if video_info:
                # 保留原始标题，同时记录顺序索引（用于后续按集命名）
                video_info['original_title'] = video_info.get('title', episode['title'])
                video_info['episode_index'] = i
                all_video_info.append(video_info)
            else:
                failed_episodes.append(episode)
                print(f"❌ 无法提取: {episode['title']}")

        # 若有失败则尝试再次提取（较短次数）
        if failed_episodes:
            print(f"\n🔁 首次提取失败 {len(failed_episodes)} 集，尝试再次提取...")
            for episode in failed_episodes[:]:  # 复制列表以便在迭代中修改
                print(f"重新尝试: {episode['title']}")
                self.random_sleep(2.0, 4.0)
                video_info = self.extract_video_url(episode['url'], episode['title'], retry_count=2)
                if video_info:
                    video_info['original_title'] = video_info.get('title', episode['title'])
                    # 需要确定 episode_index：尝试在原 episodes 列表中寻找索引
                    # 若无法找到，附加到末尾
                    try:
                        idx = next((i + 1 for i, e in enumerate(episodes) if e['url'] == episode['url']), None)
                        video_info['episode_index'] = idx or (len(all_video_info) + 1)
                    except Exception:
                        video_info['episode_index'] = (len(all_video_info) + 1)
                    all_video_info.append(video_info)
                    failed_episodes.remove(episode)
                    print(f"✅ 重新提取成功: {episode['title']}")
                else:
                    print(f"❌ 重新提取失败: {episode['title']}")

        return all_video_info, failed_episodes

    def auto_download_video(self, video_info, keyboard_tabs=5, keyboard_enters=2):
        """
        自动下载单个视频：
        - 在新标签页打开视频 URL（或 referer 页面），模拟 Tab/Enter 操作触发下载
        - 检测下载目录中新文件并返回 True/False
        - 尽量不关闭主窗口（但会关闭打开的临时标签）
        - **修改：移除下载后等待，仅返回是否成功触发**
        """
        title = video_info.get('title', video_info.get('original_title', '未知标题'))
        video_url = video_info.get('video_url')
        referer = video_info.get('referer')

        print(f"\n⬇️ 开始自动下载: {title}")
        print(f"  - 视频 URL: {video_url}")

        try:
            original_window = self.driver.current_window_handle

            # 打开新标签页
            self.driver.execute_script("window.open('');")
            new_handles = [h for h in self.driver.window_handles if h != original_window]
            if not new_handles:
                print("  ❌ 无法获取新标签页句柄")
                return False
            new_window = new_handles[0]
            self.driver.switch_to.window(new_window)

            # 访问视频 URL（有些站点需要 referer，先访问 referer 再打开 iframe URL）
            try:
                # 直接访问 video_url 可能会被浏览器直接打开播放，仍然可以尝试
                self.driver.get(video_url)
            except Exception:
                # 若直接打开失败，回退到 referer 页面（若存在）
                if referer:
                    try:
                        self.driver.get(referer)
                    except Exception:
                        pass

            # 等待页面稳定（适度）
            # 移除了此处的 random_sleep，因为它发生在按键操作之前，可能不必要
            # self.random_sleep(2.0, 4.0)

            # 记录当前下载目录初始文件集合
            initial_files = set(os.listdir(self.download_dir)) if os.path.exists(self.download_dir) else set()

            # 模拟键盘操作（Tab / Enter）尝试触发下载
            print("  - 开始模拟按键操作以触发下载（Tab -> Enter）")
            actions = ActionChains(self.driver)

            # 模拟 Tab 键多次（通过单次 perform() 实际发送）
            for i in range(keyboard_tabs):
                try:
                    actions.send_keys(Keys.TAB).perform()
                except Exception:
                    # 有时候同一 ActionChains 重用可能失败，重新创建一个
                    actions = ActionChains(self.driver)
                    actions.send_keys(Keys.TAB).perform()
                print(f"    - 按下 Tab ({i + 1}/{keyboard_tabs})")
                self.random_sleep(0.5, 1.2)

            # 模拟 Enter 键多次
            for i in range(keyboard_enters):
                try:
                    actions.send_keys(Keys.ENTER).perform()
                except Exception:
                    actions = ActionChains(self.driver)
                    actions.send_keys(Keys.ENTER).perform()
                print(f"    - 按下 Enter ({i + 1}/{keyboard_enters})")
                self.random_sleep(0.8, 1.6)

            print("  - 按键模拟完成，等待下载开始...")

            # 等待一段短时间观察是否有新文件出现
            # 移除了此处的 random_sleep，因为后续的文件检查逻辑会处理等待
            # self.random_sleep(2.0, 4.0)

            # 检查是否有新增文件（包括 .crdownload）
            current_files = set(os.listdir(self.download_dir)) if os.path.exists(self.download_dir) else set()
            new_files = current_files - initial_files

            # 如果没有检测到新增文件，等待更长再检测
            if not new_files:
                print("  ⚠️ 未立即检测到新文件，继续等待并再次检查（最多等待 30s）")
                # 扩展短等待循环以确保下载有时间启动
                for _ in range(6):
                    self.random_sleep(1.0, 2.0)
                    current_files = set(os.listdir(self.download_dir))
                    new_files = current_files - initial_files
                    if new_files:
                        break

            if new_files:
                print(f"  ⏳ 检测到新文件: {new_files}")
                # 若有任何 .crdownload，则下载已启动
                downloading = [f for f in new_files if f.endswith('.crdownload')]
                if downloading:
                    print(f"  ⏳ 检测到下载中 (.crdownload)：{downloading}，已成功触发下载。")
                    # 关闭临时标签并返回 True，表示已触发
                    try:
                        self.driver.close()
                    except Exception:
                        pass
                    try:
                        self.driver.switch_to.window(original_window)
                    except Exception:
                        pass
                    return True
                else:
                    # 新文件存在但无 .crdownload -> 可能已经完成或非分段下载
                    print(f"  ✅ 新文件已生成（无 .crdownload）：{new_files}，可能已下载完成或触发成功。")
                    # 关闭临时标签并返回 True
                    try:
                        self.driver.close()
                    except Exception:
                        pass
                    try:
                        self.driver.switch_to.window(original_window)
                    except Exception:
                        pass
                    return True
            else:
                # 未检测到新增文件
                print("  ❌ 未检测到任何新增文件，下载可能未触发或被阻止")
                # 关闭临时标签并返回失败
                try:
                    self.driver.close()
                except Exception:
                    pass
                try:
                    self.driver.switch_to.window(original_window)
                except Exception:
                    pass
                return False

        except Exception as e:
            print(f"❌ 自动下载出错 {title}: {e}")
            print(traceback.format_exc())
            try:
                self.driver.switch_to.default_content()
            except Exception:
                pass
            return False

    def auto_download_all_videos(self, all_video_info):
        """
        顺序自动下载所有视频（调用 auto_download_video）。
        每一次下载前后会有随机间隔。下载任务全部提交后，等待 300s。
        最后对下载目录进行按时间排序的重命名（第1集、第2集...）。
        """
        print(f"\n🚀 开始自动下载 {len(all_video_info)} 个视频...")

        success_count = 0
        failed_count = 0

        for i, video_info in enumerate(all_video_info, 1):
            print(f"\n[{i}/{len(all_video_info)}] 下载: {video_info.get('title', video_info.get('original_title', '未知'))}")

            # 随机短等待 (2-4秒)，以规避反爬
            if i > 1:
                d = self.random_sleep(2.0, 4.0) # 修改等待时间范围
                print(f"  - 随机等待 {d:.2f}s (规避反爬)")

            ok = self.auto_download_video(video_info)
            if ok:
                success_count += 1
                print(f"✅ 视频已开始下载: {video_info.get('title')}")
            else:
                failed_count += 1
                print(f"❌ 下载触发失败: {video_info.get('title')}")

        print(f"\n✅ 所有 {len(all_video_info)} 个视频下载任务已提交。")
        print(f"  - 成功触发: {success_count}")
        print(f"  - 触发失败: {failed_count}")

        print("\n⏳ 等待所有下载任务完成 (最长 300 秒) ...")
        # 等待 300 秒，确保所有下载有足够时间完成
        if self.wait_for_download_complete(timeout=300):
             print("✅ 所有下载任务等待完成或超时。")
        else:
             print("⚠️ 等待所有下载完成超时，部分文件可能仍在写入。")

        # 下载后重命名文件为 第1集、 第2集 ...
        self.rename_downloaded_files_by_episode(all_video_info)

        print("\n📊 下载结束统计：")
        print(f"  ✅ 成功触发: {success_count}")
        print(f"  ❌ 触发失败: {failed_count}")
        print(f"  📁 下载目录: {os.path.abspath(self.download_dir)}")

        return success_count, failed_count

    def wait_for_download_complete(self, timeout=300, check_interval=5):
        """
        检测下载目录中的 .crdownload 文件，并判断下载是否完成。
        - timeout: 最大等待时间（秒）
        - check_interval: 检查间隔（秒）
        如果在 timeout 时间内没有任何 .crdownload 且存在新文件则认为完成，返回 True。
        否则超时返回 False。
        """
        print("🕐 等待下载完成...")
        start_time = time.time()

        # 记录开始检查时目录中的文件
        initial_files = set(os.listdir(self.download_dir)) if os.path.exists(self.download_dir) else set()

        while time.time() - start_time < timeout:
            current_files = set(os.listdir(self.download_dir)) if os.path.exists(self.download_dir) else set()
            new_files = current_files - initial_files

            # 是否存在 .crdownload（Chrome 未完成下载临时后缀）
            cr_files = [f for f in current_files if f.endswith('.crdownload')]

            # 如果存在任何 .crdownload，则认为仍在下载
            if cr_files:
                remaining = timeout - (time.time() - start_time)
                print(f"  ⏳ 发现 {len(cr_files)} 个 .crdownload 文件，剩余等待时间: {int(remaining)}s")
                time.sleep(check_interval)
                continue

            # 如果没有 .crdownload 并且有新增文件，视为完成
            if new_files:
                print("  ✅ 未检测到 .crdownload，且发现新文件，下载可能已完成。")
                return True

            # 没有新增文件，也没有 .crdownload：继续等待（可能尚未开始或已完成但文件名已变）
            remaining = timeout - (time.time() - start_time)
            print(f"  ⏳ 暂未发现新文件或 .crdownload，剩余等待时间: {int(remaining)}s")
            time.sleep(check_interval)

        # 超时
        print("⏱️ 下载等待超时")
        return False

    def rename_downloaded_files_by_episode(self, all_video_info):
        """
        将下载目录中按修改时间排序的文件，依次重命名为 '第{episode_index}集.ext'
        使用 all_video_info 中的 episode_index 作为优先顺序映射（若 possible）
        逻辑：
          1) 先尝试根据 existing filename 包含某些 index 信息映射（若能识别）
          2) 否则按文件修改时间排序，依次映射到第1、2、3...
        注意：如果文件数少于视频数，会输出警告。
        """
        try:
            # 列出目录中常见视频扩展名文件（包括临时 .crdownload 的处理）
            video_files = []
            for f in os.listdir(self.download_dir):
                lower = f.lower()
                # 接受 mp4,mkv,flv,ts,webm 等常见格式
                if lower.endswith(('.mp4', '.mkv', '.flv', '.ts', '.webm', '.avi', '.mov')):
                    video_files.append(f)

            # 如果没有检测到任何视频文件，尝试排除 .crdownload 并查看最近创建的文件
            if not video_files:
                # 尝试把已经完成但后缀奇怪的文件也纳入考虑（排除 .crdownload）
                all_files = [f for f in os.listdir(self.download_dir) if not f.endswith('.crdownload')]
                all_files.sort(key=lambda x: os.path.getmtime(os.path.join(self.download_dir, x)))
                video_files = all_files

            # 使用修改时间排序（旧到新）
            video_files.sort(key=lambda x: os.path.getmtime(os.path.join(self.download_dir, x)))

            if not video_files:
                print("⚠️ 未检测到任何可重命名的视频文件。")
                return False

            print(f"🗂️ 检测到 {len(video_files)} 个视频文件，将尝试按集次重命名...")

            # 我们想尽量把文件按 episode_index 对应到正确集数：
            # 若 all_video_info 中保存了 episode_index，可以按 episode_index 升序来映射
            # 否则按文件时间顺序映射到第1..N
            # 先生成一个期望的集次顺序列表（长度 = 视频数）
            expected_indices = []
            # 构造映射：episode_index -> desired name
            for info in sorted(all_video_info, key=lambda x: x.get('episode_index', 0)):
                idx = info.get('episode_index')
                if idx:
                    expected_indices.append(idx)

            # 如果 expected_indices 的长度等于或大于 video_files 的长度并且唯一，则直接基于索引重命名
            use_index_map = False
            if expected_indices and len(expected_indices) >= len(video_files):
                # 去重检查
                uniq = sorted(set(expected_indices))
                if len(uniq) >= len(video_files):
                    use_index_map = True

            # 如果能按 index 映射，就先 build target names
            if use_index_map:
                # 依照 expected_indices 的最小 N 个来对文件排序
                # 比较稳妥的方法：以文件修改时间排序，然后依次对 expected_indices 的最小 N 项进行命名
                sorted_files = video_files
                sorted_files.sort(key=lambda x: os.path.getmtime(os.path.join(self.download_dir, x)))
                # 取 expected_indices 排序后最小 len(sorted_files) 个
                unique_sorted_indices = sorted(sorted(set(expected_indices)))
                map_indices = unique_sorted_indices[:len(sorted_files)]
                for i, filename in enumerate(sorted_files):
                    target_index = map_indices[i]
                    ext = os.path.splitext(filename)[1]
                    new_name = f"第{target_index}集{ext}"
                    src = os.path.join(self.download_dir, filename)
                    dst = os.path.join(self.download_dir, new_name)
                    # 若目标文件已存在，附加后缀避免覆盖
                    if os.path.exists(dst):
                        dst = os.path.join(self.download_dir, f"第{target_index}集_{int(time.time())}{ext}")
                    os.rename(src, dst)
                    print(f"  - 重命名: {filename} -> {os.path.basename(dst)}")
            else:
                # 按修改时间顺序将文件重命名为第1..N集
                sorted_files = video_files
                sorted_files.sort(key=lambda x: os.path.getmtime(os.path.join(self.download_dir, x)))
                for i, filename in enumerate(sorted_files, 1):
                    ext = os.path.splitext(filename)[1]
                    new_name = f"第{i}集{ext}"
                    src = os.path.join(self.download_dir, filename)
                    dst = os.path.join(self.download_dir, new_name)
                    if os.path.exists(dst):
                        dst = os.path.join(self.download_dir, f"第{i}集_{int(time.time())}{ext}")
                    os.rename(src, dst)
                    print(f"  - 重命名: {filename} -> {os.path.basename(dst)}")

            # 检查文件数量与预期的差异
            expected_count = len(all_video_info)
            actual_count = len(video_files)
            if actual_count < expected_count:
                print(f"⚠️ 实际检测到 {actual_count} 个视频文件，小于提取到的 {expected_count} 集（可能部分下载失败或尚未完成）。")

            return True

        except Exception as e:
            print(f"❌ 重命名失败: {e}")
            print(traceback.format_exc())
            return False

    def save_batch_video_info(self, all_video_info, series_title="视频系列", failed_episodes=None, output_dir="video_urls"):
        """
        将提取到的视频信息保存到 output_dir 中（urls.txt, all_info.json, 每集单独 url/info）。
        """
        if failed_episodes is None:
            failed_episodes = []

        try:
            if not os.path.exists(output_dir):
                os.makedirs(output_dir, exist_ok=True)

            safe_series_title = "".join(c for c in series_title if c.isalnum() or c in (' ', '-', '_')).rstrip()

            urls_file = os.path.join(output_dir, f"{safe_series_title}_urls.txt")
            with open(urls_file, 'w', encoding='utf-8') as f:
                for video_info in all_video_info:
                    f.write(f"{video_info.get('original_title', video_info.get('title'))}|{video_info.get('video_url')}|{video_info.get('referer')}\n")

            info_file = os.path.join(output_dir, f"{safe_series_title}_all_info.json")
            with open(info_file, 'w', encoding='utf-8') as f:
                json.dump({
                    'series_title': series_title,
                    'success_count': len(all_video_info),
                    'failed_count': len(failed_episodes),
                    'success_episodes': all_video_info,
                    'failed_episodes': failed_episodes,
                    'extract_time': time.strftime('%Y-%m-%d %H:%M:%S')
                }, f, ensure_ascii=False, indent=2)

            # 单条保存
            for video_info in all_video_info:
                safe_title = "".join(c for c in video_info.get('original_title', video_info.get('title')) if c.isalnum() or c in (' ', '-', '_')).rstrip()
                url_file = os.path.join(output_dir, f"{safe_title}_url.txt")
                with open(url_file, 'w', encoding='utf-8') as f:
                    f.write(video_info.get('video_url', ''))

                single_info_file = os.path.join(output_dir, f"{safe_title}_info.json")
                with open(single_info_file, 'w', encoding='utf-8') as f:
                    json.dump(video_info, f, ensure_ascii=False, indent=2)

            # 失败列表
            if failed_episodes:
                failed_file = os.path.join(output_dir, f"{safe_series_title}_failed_episodes.txt")
                with open(failed_file, 'w', encoding='utf-8') as f:
                    f.write(f"未能提取的视频列表 ({len(failed_episodes)} 个):\n\n")
                    for episode in failed_episodes:
                        f.write(f"{episode.get('title')}\n")
                        f.write(f"播放页面: {episode.get('url')}\n\n")

            print(f"\n✅ 批量视频信息已保存到: {output_dir}/")
            return True

        except Exception as e:
            print(f"❌ 保存批量视频信息失败: {e}")
            print(traceback.format_exc())
            return False

    def close(self):
        """结束并关闭浏览器"""
        try:
            if self.driver:
                self.driver.quit()
                print("浏览器已关闭")
        except Exception:
            pass


def main():
    # 创建下载目录
    download_dir = "auto_downloads"
    if not os.path.exists(download_dir):
        os.makedirs(download_dir, exist_ok=True)

    downloader = AutoVideoDownloader(download_dir)

    try:
        # 获取用户输入的剧集详情页
        detail_url = input("请输入剧集详情页URL (例如: https://www.libvio.cc/detail/...  ): ").strip()
        if not detail_url:
            print("❌ 未提供URL，程序退出")
            return

        # 尝试从详情页获取系列标题（用于保存文件）
        series_title = "视频系列"
        if "detail/" in detail_url:
            try:
                downloader.driver.get(detail_url)
                # 尝试读取 title 标签内容
                try:
                    title_element = downloader.driver.find_element(By.TAG_NAME, "title")
                    page_title = title_element.get_attribute("textContent")
                    if page_title:
                        # 处理常见包含播放字样的标题
                        if "播放" in page_title:
                            series_title = page_title.split("播放")[0].strip()
                        else:
                            # 否则取完整 title 的前一部分或全部
                            series_title = page_title.strip()
                except Exception:
                    # 忽略 title 获取失败
                    pass
            except Exception:
                pass

        print("获取剧集列表...")
        episodes = downloader.get_episode_list(detail_url)
        if not episodes:
            print("❌ 未找到剧集列表或页面结构不匹配")
            return

        print(f"找到 {len(episodes)} 个剧集:")
        for ep in episodes:
            print(f"  - {ep.get('title')}")

        confirm = input(f"\n是否开始提取所有 {len(episodes)} 个视频地址? (y/n): ").strip().lower()
        if confirm != 'y':
            print("已取消操作")
            return

        # 顺序提取
        all_video_info, failed_episodes = downloader.extract_all_video_urls_sequential(episodes)
        if not all_video_info:
            print("❌ 未能提取到任何视频地址")
            return

        print(f"\n🎉 批量提取完成!")
        print(f"成功提取 {len(all_video_info)}/{len(episodes)} 个视频地址")
        if failed_episodes:
            print(f"失败 {len(failed_episodes)} 个视频地址，详情会保存到文件")

        print("\n📊 提取结果摘要:")
        for v in all_video_info:
            idx = v.get('episode_index', '?')
            print(f"  ✅ 第{idx}集 -> {v.get('video_url')[:100]}{'...' if len(v.get('video_url',''))>100 else ''}")

        if failed_episodes:
            for ep in failed_episodes:
                print(f"  ❌ {ep.get('title')}")

        # 保存提取信息
        downloader.save_batch_video_info(all_video_info, series_title, failed_episodes)

        # 询问是否自动下载
        auto_download = input(f"\n是否开始自动下载 {len(all_video_info)} 个视频? (y/n): ").strip().lower()
        if auto_download == 'y':
            success_count, failed_count = downloader.auto_download_all_videos(all_video_info)
            print(f"\n自动下载完成!")
            print(f"✅ 成功触发: {success_count} 个视频")
            print(f"❌ 触发失败: {failed_count} 个视频")
            print(f"📁 下载目录: {os.path.abspath(download_dir)}")
        else:
            print("已跳过自动下载，程序结束。")

    finally:
        downloader.close()


if __name__ == "__main__":
    main()